const express = require('express');

//处理post请求
const bodyParser = require('body-parser');

//处理上传文件
const uploader = require('express-fileupload');

//处理文件加密
const CryptoJS = require('crypto-js');

//处理视频切片文件路径引导
const ffmpegPath = require('@ffmpeg-installer/ffmpeg').path;

//处理视频的库
const ffmpeg = require('fluent-ffmpeg');

//处理文件路径
const { resolve } = require('path');

//读取文件 / 写入文件 / 文件内容追加 / 判断文件是否存在 / 文件删除 / 目录删除 / 目录读取 / 目录创建
const {
  readFileSync,
  writeFileSync,
  appendFileSync,
  existsSync,
  unlinkSync,
  rmdirSync,
  readdirSync,
  mkdirSync
} = require('fs');

//引入工具
const { formatVideoToM3u8 } = require('./utils/tools');

const app = express();
const PORT = 8000;

const ALLOWED_TYPES = {
  'video/mp4': 'mp4',
  'video/ogg': 'ogg',
  hls: 'm3u8'
};

//获取临时目录的相对路径
const tempDir = resolve(__dirname, './temp/');
//获取视频目录的相对路径
const videoDir = resolve(__dirname, './videos/');

app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(uploader());

//设置静态路径
//http://localhost:8000/xxxx/xxx.m3u8
app.use(
  '/',
  express.static('videos', {
    //跨越允许
    setHeaders(res) {
      res.set('Access-Control-Allow-Origin', '*');
    }
  })
);

ffmpeg.setFfmpegPath(ffmpegPath);

//请求跨域
app.all('*', (req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Methods', 'POST,GET');
  next();
});

//API
//上传
app.post('/upload_video', (req, res) => {
  const { name, type, size, chunkName } = req.body;
  //req.files是express-fileupload中间件的返回chunk对象
  const { chunk } = req.files;
  /**
   打印返回的一个req.files.chunk对象
   console.log(req.files);
   * chunk: {
   *   name: 'blob',
   *   data: <Buffer c9 1f 00  ... 65486 more bytes>,
   *   size: 65536,
   *   encoding: '7bit',
   *   tempFilePath: '',
   *   truncated: false,
   *   mimetype: 'application/octet-stream',
   *   md5: '7c8a439ddc4164dc28ea624cf6c8a7fa',
   *   mv: [Function: mv]
   * }
   */
  //加密文件名称
  const fileName = CryptoJS.MD5(name);
  //生成的临时切片文件目录
  const tempFilesDir = tempDir + '/' + fileName;

  //当没有传chunk时
  if (!chunk) {
    res.send({
      code: 1001,
      msg: 'No file uploaded.'
    });
    return;
  }

  //校验数据格式
  if (!ALLOWED_TYPES[type]) {
    res.send({
      code: 1002,
      msg: 'The type is not allowed for uploading.'
    });
    return;
  }

  //如果不存在目录时
  if (!existsSync(tempFilesDir)) {
    //创建目录
    mkdirSync(tempFilesDir);
  }

  //将所有chunk块存入一个临时目录
  //将chunk对象的data作为文件内容写入指定路径文件(无后缀)
  //temp/fileName/chunkName
  writeFileSync(tempFilesDir + '/' + chunkName, chunk.data);

  //所有切片上传成功后响应文件名称
  res.send({
    code: 0,
    msg: 'Chunks are all uploaded.',
    filename: fileName.toString()
  });
});

//合并(后端无法知道是否上传完毕时，需要前端发送请求要求合并)
//将临时目录里的所有chunk合并成一个mp4文件
app.post('/merge_video', (req, res) => {
  const { filename, type } = req.body,
    tempFilesDir = tempDir + '/' + filename,
    videoFileDir = videoDir + '/' + filename,
    //读取目录获取文件列表数组容器
    fileList = readdirSync(tempFilesDir);

  /**
   * console.log(fileList);
   * [
   *   '1658238700133_study_lesson1',
   *   '1658238700179_study_lesson1',
   *   '1658238700196_study_lesson1',
   *   ...
   * ]
   */

  //没有目录时
  if (!existsSync(videoFileDir)) {
    //创建目录 /videos/xxxxxxxxxxxx/
    mkdirSync(videoFileDir);
  }

  //组装mp4文件路径
  const mp4Path = `${videoFileDir}/${filename}.${ALLOWED_TYPES[type]}`;
  //组装hls文件路径
  const hlsPath = `${videoFileDir}/${filename}.${ALLOWED_TYPES['hls']}`;
  // console.log(hlsPath);
  //videos/71c998648e94626e4f154543e86f70c8/71c998648e94626e4f154543e86f70c8.m3u8

  //遍历
  fileList.forEach((chunk) => {
    //写入内容
    const chunkPath = `${tempFilesDir}/${chunk}`;
    const chunkContent = readFileSync(chunkPath);

    /**
     * console.log(chunkContent);
     * <Buffer 6f c3 54 1b e1 20 95 e5 87 1c  ... 65486 more bytes>
     * <Buffer 6f c3 54 1b e1 20 95 e5 87 1c  ... 65486 more bytes>
     * <Buffer 6f c3 54 1b e1 20 95 e5 87 1c  ... 65486 more bytes>
     * ...
     */

    //没有文件时
    if (!existsSync(mp4Path)) {
      //写入文件 将所有chunk块组装成一个mp4文件
      writeFileSync(mp4Path, chunkContent);
    } else {
      //存在文件时
      appendFileSync(mp4Path, chunkContent);
    }

    //删除temp/xxxxx/文件夹里所有内容
    unlinkSync(chunkPath);
  });

  //删除temp/xxxx/文件夹
  rmdirSync(tempFilesDir);

  //将videos/xxxxx/xxxxx.mp4文件切片为多个m3u8的视频格式文件
  formatVideoToM3u8(mp4Path, ffmpeg, {
    videoCodec: 'libx264',
    format: 'hls',
    outputOptions: '-hls_list_size 0',
    outputOption: '-hls_time 5',
    output: hlsPath,
    onError(e) {
      //删除整个videos文件夹
      const fileList = readFileSync(videoFileDir);

      fileList.forEach((chunk) => {
        const chunkPath = `${videoFileDir}/${chunk}`;
        unlinkSync(chunkPath);
      });

      rmdirSync(videoFileDir);

      res.send({
        code: 1006,
        msg: e.message
      });
    },
    onEnd() {
      res.send({
        code: 0,
        msg: 'Upload successfully.',
        // http://localhost:8000/xxxxxxxx/xxxx.m3u8
        videoSrc: `http://localhost:8000/${filename}/${filename}.${ALLOWED_TYPES['hls']}`
      });
    }
  });

  // res.send({
  //   msg: 'ok'
  // });
});

app.listen(PORT, () => {
  console.log(`Server is running on ${PORT}`);
});
